package mhserver;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Hashtable;
import java.util.concurrent.LinkedBlockingQueue;

import javax.swing.JOptionPane;

import mh.log.MHLogger;
import mh.message.MessageValidator;

/**
 * This class is the main class for the Server that holds the game. Now is a single point server.<br>
 * TODO Create a multi-node server.<br>
 * TODO Create an autobackup server.<br>
 * <br>
 * 
 * @author <a href="mailto:gianmarco.laggia@gmail.com">Gianmarco Laggia</a>
 * 
 */
public class MagicHogwartsServer implements Runnable {

	// ==================================================
	//                  CONNECTION DATA
	// ==================================================
	/**
	 * The port where the server is listening at. I use this for the <code>ServerSocket</code>.<br>
	 * The port seems not to be used. I hope so.
	 */
	private final int												PORT		= 43000;
	/**
	 * The time where the server was started.
	 */
	private long													startedAt	= 0;
	private Socket													client;				// we can use also a non global var.

	// ==================================================
	//                    SERVER DATA
	// ==================================================
	/**
	 * The name of this server, in future the name of the node.
	 */
	private String													servername;
	/**
	 * The version running of the server.
	 */
	private String													version;
	/**
	 * It contains day, month, year and at what time the server started up
	 */
	private final Date												creation;

	// ==================================================
	//                   RUNNING DATA
	// ==================================================
	/**
	 * Boolean var used to cycle the incoming connection listener
	 */
	private boolean													running;
	/**
	 * Queue of the incoming messages. Used by <code>MessageExecutor</code>.
	 */
	private final LinkedBlockingQueue<Pair<String, ClientListener>>	messageQueue;
	/**
	 * The hashtable containing all connected players as ClientListener(key), Player(value)
	 */
	private final Hashtable<ClientListener, Player>					clientPlayerHashtable;
	/**
	 * The hashtable containing all connected players as Player(key), ClientListener(value)
	 */
	private final Hashtable<String, ClientListener>					playerConnections;

	// ==================================================
	// INCOMING MESSAGE HANDLERS
	// ==================================================
	/**
	 * This object is the msg analyzer. It execute every message, also replying to the user.
	 */
	private MessageExecutor											msgExecutor;
	/**
	 * The thread of the <code>MessageExecutor</code>
	 */
	private Thread													msgThread;

	/**
	 * Entry point of the game.
	 * 
	 * @param arg
	 *            Not used
	 */
	public static void main(String[] arg) {

		try {
			MHLogger.setup();
		} catch (IOException e) {
			JOptionPane.showMessageDialog(null, "Error in the setup of the logger.\n- ERROR 004 -");
		}

		new Thread(new MagicHogwartsServer(), "MagicHogwarts Server").start();
	}

	/**
	 * Main constructor, initialize all basic parameters and stuff from a file named "config.txt".
	 */
	public MagicHogwartsServer() {
		new MessageValidator();
		this.running = false;
		this.creation = new Date();
		this.clientPlayerHashtable = new Hashtable<ClientListener, Player>();
		this.playerConnections = new Hashtable<String, ClientListener>();
		this.messageQueue = new LinkedBlockingQueue<Pair<String, ClientListener>>();

		//		new Database("magichogwarts", "mhdb");
		//		SwingUtilities.invokeLater(new Runnable() {
		//			@Override
		//			public void run() {
		//				Database.start();
		//				//				new GUI();
		//			}
		//		});

		//		try {
		//			Class.forName("org.h2.Driver");
		//			Connection conn;
		//			conn = DriverManager.getConnection("jdbc:h2:~/test", "sa", "");
		//			conn.close();
		//		} catch (SQLException e) {
		//			// TODO Auto-generated catch block
		//			e.printStackTrace();
		//		} catch (ClassNotFoundException e) {
		//			// TODO Auto-generated catch block
		//			e.printStackTrace();
		//		}
		// READ BASIC INFORMATION FROM THE DATABASE

	}

	@Override
	public void run() {
		// MESSAGE EXECUTOR
		this.msgExecutor = new MessageExecutor(this);
		this.msgThread = new Thread(this.msgExecutor, "MHServer.MessageThread");

		this.running = true;
		this.startedAt = System.currentTimeMillis();

		while (this.running) {

			try {
				Thread.sleep(10);
			} catch (InterruptedException e) {
				//
			}
			try {

				// ****** DEBUG ****** //
				System.out.println("[MHServer] MagicHogwart's Server starting on port: " + this.PORT);
				// ******************* //

				ServerSocket server = new ServerSocket(this.PORT);
				server.setSoTimeout(1000);

				this.msgThread.start();
				Thread.yield();

				// ****** DEBUG ****** //
				System.out.println("[MHServer] MessageExecutor's thread started with name: " + this.msgThread.getName());
				// ******************* //

				this.startedAt = System.currentTimeMillis();

				// ****** DEBUG ****** //
				System.out.println("[MHServer] Waiting for incoming connections.");
				// ******************* //

				while (this.running) {

					try {
						this.client = server.accept();

						// ****** DEBUG ****** //
						System.out.println("[MHServer] New connection incoming");
						// ******************* //

						if (this.client != null) {

							// ****** DEBUG ****** //
							System.out.println("[MHServer] Connection asked from: " + this.client.getInetAddress());
							// ******************* //

							ClientListener cl = new ClientListener(this.client, this);

							Thread clientThread = new Thread(cl);
							clientThread.start();

							if (clientThread.isAlive()) {

								// ****** DEBUG ****** //
								System.out.println("[MHServer] New ClientListener for the incoming connection is now started.");
								// ******************* //

							}

							else {

								// ****** DEBUG ****** //
								System.out.println("[MHServer] Failed to create a new ClientListener, its thread is dead.");
								System.out.println("[MHServer] Connection from " + this.client.getInetAddress() + " refused. Thread could not start.");
								// ******************* //

								this.client.close();
								clientThread = null;
								cl = null;
							}

						} else {

							// ****** DEBUG ****** //
							System.out.println("[MHServer] Failed to create a new socket (is null). Connection refused.");
							// ******************* //
						}
					} catch (SocketTimeoutException e) {
						// used to reiterate the cycle for the consistency check

					}
				}
				server.close();

			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			System.out.println("[MHServer] This should not be happened. InterruptedException");
			e.printStackTrace();
		}
	}

	/**
	 * The date in the format: "EEE d MMMMM yyyy HH:mm:ss" in which the server was created
	 * 
	 * @return The date of creation in String.
	 */
	public String getCreatedAt() {
		DateFormat dateFormat = new SimpleDateFormat("EEE d MMMMM yyyy HH:mm:ss");
		return dateFormat.format(this.creation);
	}

	/**
	 * Method used to add message to the queue.
	 * 
	 * @param msg
	 *            The message to enqueue
	 * @param cl
	 *            The client listener
	 */
	public void enqueueMessage(String msg, ClientListener cl) {
		this.messageQueue.add(new Pair<String, ClientListener>(msg, cl));
	}

	/**
	 * Used to get a new message from the queue.
	 * 
	 * @return The new message
	 * @throws InterruptedException
	 */
	public Pair<String, ClientListener> getMessage() throws InterruptedException {

		return this.messageQueue.take();

	}

	/**
	 * Returns the hashtable that link each player to his client
	 * 
	 * @return The <code>Hashtable</code>
	 */
	public Hashtable<ClientListener, Player> getClientPlayerHashtable() {
		return this.clientPlayerHashtable;
	}

	/**
	 * Add a new player in the hashtable. This method should be called by the cl when the user specify his name as first
	 * message.
	 * 
	 * @param cl
	 * @param name
	 */
	public void addUser(ClientListener cl, Player player) {
		if ((cl == null) || (player == null)) {
			throw new IllegalArgumentException();
		}
		this.clientPlayerHashtable.put(cl, player);
		this.playerConnections.put(player.getNickname(), cl);
	}

	/**
	 * Returns true if the name is available, false if not.
	 * 
	 * @param name
	 * @return true if available
	 */
	public boolean checkName(String name) {
		return !this.playerConnections.containsKey(name);
	}

	/**
	 * Remove an user from the players hashtable
	 * 
	 * @param cl
	 * @param name
	 */
	public void removeUser(ClientListener cl, String name) {
		if ((cl == null) || (name == null) || "".equals(name)) {
			throw new IllegalArgumentException();
		}
		if (this.clientPlayerHashtable.containsKey(cl) && this.playerConnections.containsKey(name)) {
			this.clientPlayerHashtable.remove(cl);
			this.playerConnections.remove(name);
		} else {
			throw new IllegalArgumentException();
		}
	}

	public void shutdown() {
	};

}
